package com.easylive.entity.component;

import com.easylive.entity.config.AppConfig;
import com.easylive.entity.dto.VideoInfoEsDTO;
import com.easylive.entity.enums.PageSize;
import com.easylive.entity.enums.SearchOrderTypeEnum;
import com.easylive.entity.po.UserInfo;
import com.easylive.entity.po.VideoInfo;
import com.easylive.entity.query.SimplePage;
import com.easylive.entity.query.UserInfoQuery;
import com.easylive.entity.vo.PaginationResultVO;
import com.easylive.exception.BusinessException;
import com.easylive.mappers.UserInfoMapper;
import com.easylive.utils.CopyTools;
import com.easylive.utils.JsonUtil;
import com.easylive.utils.StringTools;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Author: yudaxian
 * @DateTime: 2024/12/11 15:41
 **/
@Component("esSearchComponent")
@Slf4j
public class EsSearchComponent {

    @Resource
    private AppConfig appConfig;

    @Resource
    private RestHighLevelClient restHighLevelClient;

    @Resource
    private UserInfoMapper<UserInfo, UserInfoQuery> userInfoMapper;

    private Boolean isExistsIndex() throws IOException {
        GetIndexRequest getIndexRequest = new GetIndexRequest(appConfig.getEsIndexVideoName());
        return restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
    }
    /**
     * 创建索引，在服务启动的时候创建
     */
    public void createIndex() {
        try {
            if (isExistsIndex()) {
                return;
            }

            CreateIndexRequest request = new CreateIndexRequest(appConfig.getEsIndexVideoName());
            /**
             * 自定义的分词器，用于处理逗号分隔的字符串（tags）
             */
            request.settings("""
                    {
                      "analysis": {
                        "analyzer": {
                          "comma": {
                            "type": "pattern",
                            "pattern": ","
                          }
                        }
                      }
                    }
                    """, XContentType.JSON);
            request.mapping("""
                    {
                      "properties": {
                        "videoId": {
                          "type": "text",
                          "index": false
                        },
                        "userId": {
                          "type": "text",
                          "index": false
                        },
                        "videoCover": {
                          "type": "text",
                          "index": false
                        },
                        "videoName": {
                          "type": "text",
                          "analyzer": "ik_max_word"
                        },
                        "tags": {
                          "type": "text",
                          "analyzer": "comma"
                        },
                        "playCount": {
                          "type": "integer",
                          "index": false
                        },
                        "danmuCount": {
                          "type": "integer",
                          "index": false
                        },
                        "collectCount": {
                          "type": "integer",
                          "index": false
                        },
                        "createTime": {
                          "type": "date",
                          "index": false
                        }
                      }
                    }
                    """, XContentType.JSON);
            CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
            Boolean acknowledged = response.isAcknowledged();
            if (!acknowledged) {
                throw new BusinessException("初始化ES失败");
            }
        } catch (Exception e) {
            log.error("初始化ES失败", e);
            throw new BusinessException("初始化ES失败");
        }
    }

    /**
     * 保存视频信息到ES
     * 审核视频调用
     * @param videoInfo
     */
    public void saveDoc(VideoInfo videoInfo) {
        try {
            if (docIsExists(videoInfo.getVideoId())) {
                // 更新
                updateDoc(videoInfo);
            } else {
                // 新增
                VideoInfoEsDTO dto = CopyTools.copy(videoInfo, VideoInfoEsDTO.class);
                /**
                 * 设置播放量默认0
                 */
                dto.setCollectCount(0);
                dto.setDanmuCount(0);
                dto.setPlayCount(0);

                /**
                 * 请求的索引名（表格）
                 */
                IndexRequest request = new IndexRequest(appConfig.getEsIndexVideoName());
                /**
                 * 每个文档都有一个唯一ID
                 * 讲保存的数据转换成JSON
                 */
                request.id(videoInfo.getVideoId())
                        .source(JsonUtil.convertObjToStr(dto), XContentType.JSON);
                restHighLevelClient.index(request, RequestOptions.DEFAULT);
            }
        } catch (Exception e) {
            log.error("保存视频信息到ES失败", e);
            throw new BusinessException("保存视频信息到ES失败");
        }
    }

    private boolean docIsExists(String id) throws IOException {
        GetRequest getRequest = new GetRequest(appConfig.getEsIndexVideoName(), id);
        GetResponse response = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
        return response.isExists();
    }

    private void updateDoc(VideoInfo videoInfo) {
        try {
            videoInfo.setCreateTime(null);
            videoInfo.setLastUpdateTime(null);

            Map<String, Object> dataMap = new HashMap<>();
            Field[] fields = videoInfo.getClass().getDeclaredFields();
            for (Field field : fields) {
                String methodName = "get" + StringTools.upperCaseFirstLetter(field.getName());
                Method method = videoInfo.getClass().getMethod(methodName);
                Object value = method.invoke(videoInfo);
                if (value != null && value instanceof String && !StringTools.isEmpty(value.toString()) || value != null && !(value instanceof String)) {
                    dataMap.put(field.getName(), value);
                }
            }
            if (dataMap.isEmpty()) {
                return;
            }
            UpdateRequest updateRequest = new UpdateRequest(appConfig.getEsIndexVideoName(), videoInfo.getVideoId());
            updateRequest.doc(dataMap);
            restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("更新视频信息到ES失败", e);
            throw new BusinessException("更新视频信息到ES失败");
        }
    }

    public void updateDocCount(String videoId, String fieldName, Integer count) {
        try {
            UpdateRequest updateRequest = new UpdateRequest(appConfig.getEsIndexVideoName(), videoId);
            Script script = new Script(ScriptType.INLINE, "painless", "ctx._source." + fieldName + " += params.count", Collections.singletonMap("count", count));
            updateRequest.script(script);
            restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        } catch (Exception e) {
            log.error("更新数量到ES失败", e);
            throw new BusinessException("更新数量到ES失败");
        }
    }

    public void deleteDoc(String videoId) {
        DeleteRequest deleteRequest = new DeleteRequest(appConfig.getEsIndexVideoName(), videoId);
        try {
            restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("删除ES的视频失败", e);
            throw new BusinessException("删除ES的视频失败");
        }
    }

    public PaginationResultVO<VideoInfo> search(Boolean highLight, String keyword, Integer orderType, Integer pageNo, Integer pageSize) {

        try {
            SearchOrderTypeEnum orderTypeEnum = SearchOrderTypeEnum.getType(orderType);

            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(QueryBuilders.multiMatchQuery(keyword, "videoName", "tags"));

            // 高亮
            if (highLight) {
                HighlightBuilder highlightBuilder = new HighlightBuilder();
                highlightBuilder.field("videoName");
                highlightBuilder.preTags("<span class='highlight'>");
                highlightBuilder.postTags("</span>");
                searchSourceBuilder.highlighter(highlightBuilder);
            }
            // 排序
            searchSourceBuilder.sort("_score", SortOrder.ASC);
            if (orderType != null) {
                searchSourceBuilder.sort(orderTypeEnum.getField(), SortOrder.DESC);
            }

            pageNo = pageNo == null ? 1 : pageNo;
            pageSize = pageSize == null ? PageSize.SIZE20.getSize() : pageSize;

            // 分页
            searchSourceBuilder.size(pageSize);
            searchSourceBuilder.from((pageNo - 1) * pageSize);

            // 执行搜索
            SearchRequest searchRequest = new SearchRequest(appConfig.getEsIndexVideoName());
            searchRequest.source(searchSourceBuilder);

            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            // 解析结果
            SearchHits hits = searchResponse.getHits();
            Integer total = (int) hits.getTotalHits().value;

            List<VideoInfo> videoInfoList = new ArrayList<>();
            List<String> userIdList = new ArrayList<>();

            for (SearchHit hit : hits.getHits()) {
                VideoInfo videoInfo = JsonUtil.convertStrToObj(hit.getSourceAsString(), VideoInfo.class);
                if (hit.getHighlightFields().get("videoName") != null) {
                    videoInfo.setVideoName(hit.getHighlightFields().get("videoName").getFragments()[0].string());
                }
                videoInfoList.add(videoInfo);
                userIdList.add(videoInfo.getUserId());
            }

            UserInfoQuery userInfoQuery = new UserInfoQuery();
            userInfoQuery.setUserIdList(userIdList);
            List<UserInfo> userInfoList = userInfoMapper.selectList(userInfoQuery);
            Map<String, UserInfo> userInfoMap = userInfoList.stream().collect(Collectors.toMap(UserInfo::getUserId, Function.identity(), (data1, data2) -> data2));
            videoInfoList.forEach(item -> {
                item.setNickName(userInfoMap.get(item.getUserId()).getNickName());
            });

            SimplePage page = new SimplePage(pageNo, total, pageSize);
            PaginationResultVO<VideoInfo> resultVO = new PaginationResultVO<>(total, pageSize, pageNo, page.getPageTotal(), videoInfoList);
            return resultVO;
        } catch (Exception e) {
            log.error("搜索ES失败", e);
            throw new BusinessException("搜索ES失败");
        }
    }
}
